package com.atrix.CinemaView.dao;

import com.atrix.CinemaView.config.FileLocationConfig;
import com.atrix.CinemaView.exception.ParamErrorException;
import com.atrix.CinemaView.pojo.Container;
import com.atrix.CinemaView.pojo.FileObj;
import com.atrix.CinemaView.pojo.dto.info.MdInfo;
import com.atrix.CinemaView.pojo.dto.info.PhotoContainerInfo;
import com.atrix.CinemaView.pojo.dto.info.VideoInfo;
import com.atrix.CinemaView.utils.FileUtil;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import java.util.stream.Collectors;

@Component
@Slf4j
@Getter
public class FileDao {

    @Autowired
    FileLocationConfig fileLocationConfig;

    List<Container<FileObj>> photoContainerList = new ArrayList<>();
    List<PhotoContainerInfo> photoInfoList = new ArrayList<>();
    int photoCount = 0;

    List<Container<FileObj>> videoContainerList = new ArrayList<>();
    List<VideoInfo> videoInfoList = new ArrayList<>();
    int videoCount = 0;

    List<Container<FileObj>> mdContainerList = new ArrayList<>();
    List<MdInfo> mdInfoList = new ArrayList<>();
    int mdCount = 0;

    /**
     * 获取视频
     *
     * @param containerIndex 视频容器索引
     * @param index          视频索引
     * @return 视频文件
     */
    public FileObj getVideo(int containerIndex, int index) {
        if (checkIndex(containerIndex, videoContainerList.size())) {
            throw new ParamErrorException();
        }
        Container<FileObj> container = this.videoContainerList.get(containerIndex);
        if (checkIndex(index, container.getSize())) {
            throw new ParamErrorException();
        }
        return container.getData().get(index);
    }

    /**
     * 获取图片
     *
     * @param containerIndex 图片容器索引
     * @param index          图片索引
     * @return 图片对象
     */
    public FileObj getPhoto(int containerIndex, int index) {
        if (checkIndex(containerIndex, photoContainerList.size())) {
            throw new ParamErrorException();
        }
        Container<FileObj> container = this.photoContainerList.get(containerIndex);
        if (checkIndex(index, container.getSize())) {
            throw new ParamErrorException();
        }
        return container.getData().get(index);
    }

    /**
     * 获取图片容器对象
     *
     * @param containerIndex 图片容器索引
     * @return 图片容器对象
     */
    public Container<FileObj> getPhotoContainer(int containerIndex) {
        if (checkIndex(containerIndex, photoContainerList.size())) {
            throw new ParamErrorException();
        } else {
            return photoContainerList.get(containerIndex);
        }
    }

    private boolean checkIndex(int index, int size) {
        return index < 0 || index >= size;
    }

    /////////// 初始化 ///////////

    @PostConstruct
    private void init() {
        // 记录初始化时间
        long l = System.currentTimeMillis();
        ExecutorService executorService = Executors.newCachedThreadPool();

        // 处理图片
        fileLocationConfig.getPhoto().stream().map(File::new).forEach((f) -> {
            AtomicInteger counter = new AtomicInteger(0);
            this.photoContainerList.addAll(
                    getAllContainer(f, executorService, FileUtil::isPhoto, counter)
            );
            this.photoCount += counter.get();
        });
        this.photoContainerList.sort(Container::compareTo);
        this.photoInfoList = this.photoContainerList.stream()
                .map(PhotoContainerInfo::new).collect(Collectors.toList());

        //　处理视频
        fileLocationConfig.getVideo().stream().map(File::new).forEach((f) -> {
            AtomicInteger counter = new AtomicInteger(0);
            this.videoContainerList.addAll(
                    getAllContainer(f, executorService, FileUtil::isVideo, counter)
            );
            this.videoCount += counter.get();
        });
        this.videoContainerList.sort(Container::compareTo);
        this.videoInfoList = this.videoContainerList.stream()
                .map(VideoInfo::new).collect(Collectors.toList());

        //　处理文档
        fileLocationConfig.getMd().stream().map(File::new).forEach((f) -> {
            AtomicInteger counter = new AtomicInteger(0);
            this.mdContainerList.addAll(
                    getAllContainer(f, executorService, FileUtil::isMd, counter)
            );
            this.mdCount += counter.get();
        });
        this.mdContainerList.sort(Container::compareTo);
        this.mdInfoList = this.mdContainerList.stream()
                .map(MdInfo::new).collect(Collectors.toList());

        log.info("图片初始化完成：读取{}张图片", this.photoCount);
        log.info("视频初始化完成，读取{}条视频", this.videoCount);
        log.info("文档初始化完成，读取{}条md文档", this.mdCount);
        log.info("初始化耗时：{}", System.currentTimeMillis() - l);
    }

    /**
     * 通过根目录文件夹获得所有文件容器
     * @param root 跟目录文件夹
     * @param executorService 线程池
     * @param fileFilter 目标文件类型的过滤函数（FileUtils）
     * @param counter 得到的文件数量
     * @return 所有文件容器
     */
    private List<Container<FileObj>> getAllContainer(File root,
                                 ExecutorService executorService,
                                 Function<File, Boolean> fileFilter,
                                 AtomicInteger counter) {
        // 文件夹队列
        ConcurrentLinkedDeque<File> workingQueue = new ConcurrentLinkedDeque<>();
        workingQueue.add(root);
        // 返回数据
        List<Container<FileObj>> res = new ArrayList<>();
        // 存放 FutureTask 的集合
        List<Future<Container<FileObj>>> futureList = new ArrayList<>();
        while (true) {
            if (workingQueue.isEmpty()) {
                break;
            } else {
                // int count = workingQueue.size();
                // for (int i = 0; i < count; i++) { // 差距不大
                while (!workingQueue.isEmpty()) {
                    File file = workingQueue.poll();
                    Future<Container<FileObj>> future = executorService.submit(() -> {
                        if (file == null) {
                            return null;
                        }
                        File[] dirs = file.listFiles(File::isDirectory);
                        File[] files = file.listFiles(File::isFile);
                        Container<FileObj> container = new Container<>(file.getName());
                        if (dirs != null) {
                            workingQueue.addAll(Arrays.asList(dirs));
                        }
                        if (files != null) {
                            for (File f : files) {
                                if (fileFilter.apply(f)) {
                                    container.addFile(new FileObj(f));
                                }
                            }
                        }
                        if (container.getSize() == 0) {
                            return null;
                        }
                        counter.addAndGet(container.getSize());
                        return container;
                    });
                    futureList.add(future);
                }
                for (Future<Container<FileObj>> future : futureList) {
                    try {
                        Container<FileObj> data = future.get();
                        if (data == null) {
                            continue;
                        }
                        res.add(data);
                    } catch (Exception e) {
                        throw new RuntimeException(e);
                    }
                }
                futureList.clear();
            }
        }
        return res;
    }
}
